/*
 * @Author: wuqingchun
 * @Date: 2024-06-21 10:14:47
 * @Description: 
 * @LastEditors: wuqingchun
 * @LastEditTime: 2024-06-22 16:38:43
 */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <syslog.h>

#include "http.h"
#include "http_error.h"
#include "http_logger.h"


CHTTPLogger::CHTTPLogger(bool bAsync, ELogLevel enLevel, size_t ulMaxSize):
    m_bAsync(bAsync),
    m_enLogLevel(enLevel),
    m_ulMaxSize(ulMaxSize)
{
}

CHTTPLogger::~CHTTPLogger()
{
    m_bThreadAsyncRunning = false;
    
    if (m_objThreadAsync.joinable())
    {
        m_objThreadAsync.join();
    }
}

int32_t CHTTPLogger::Init(const char* pcPath)
{
    if (pcPath == nullptr || pcPath[0] == '\0')
    {
        return HTTP_INVALID_PARAM;
    }

    (void)strcpy(m_szPath, pcPath);
    (void)snprintf(m_szFileName, PATH_MAX, "%s/%s.log", pcPath, HTTP_APP_NAME);

    if (m_bAsync)
    {
        m_bThreadAsyncRunning = true;
        m_objThreadAsync = std::thread(&CHTTPLogger::__ThreadFuncAsync, this);
    }
    else
    {
        FILE* pstFile = fopen(m_szFileName, "w+");
        if (pstFile == nullptr)
        {
            return HTTP_OPEN_FILE_FAIL;
        }

        m_pstFile = pstFile;
    }

    return HTTP_OK;
}

void CHTTPLogger::Output(ELogLevel enLevel, int32_t iErrCode, const char* pcLine, const char* pcFunc, const char* pcFmt, ...)
{
    if (enLevel < m_enLogLevel)
    {
        // 当前需要输出的日志信息对应的日志级别小于服务设置的日志级别
        return;
    }

    HTTP_STRING_S stMsg;
    va_list args;
    va_start(args, pcFmt);
    int32_t iErr = __FormatLog(stMsg, enLevel, iErrCode, pcLine, pcFunc, pcFmt, args);
    va_end(args);
    if (iErr != HTTP_OK)
    {

        return;
    }

    if (m_bAsync)
    {
        // 异步写日志，将拼装好的日志信息加入日志信息队列
        m_objLogQueue.EnQueue(stMsg);
    }
    else
    {
        __WriteLog(stMsg);
    }
}

void CHTTPLogger::Syslog(const char *format, ...)
{
    int priority = LOG_SYSLOG|LOG_EMERG;
    va_list ap;

    va_start(ap, format);
    vsyslog(priority, format, ap);
    va_end(ap);
}

void CHTTPLogger::__ThreadFuncAsync()
{
    // 修改线程名称
    prctl(PR_SET_NAME, "async_logger");

    HTTP_STRING_S stMsg;

    Output(ELogLevel::LEVEL_INFO, HTTP_OK, LOG_POSITION, "Thread async_logger is running");

    while (m_bThreadAsyncRunning)
    {
        // 从队列中获取日志信息
        if (!m_objLogQueue.DeQueue(stMsg))
        {
            continue;
        }

        // 输出日志信息
        this->__WriteLog(stMsg);

        __FreeBuffer(stMsg.pcStr);
    }

    Output(ELogLevel::LEVEL_INFO, HTTP_OK, LOG_POSITION, "Thread async_logger will exit");
    while (m_objLogQueue.DeQueue(stMsg))
    {
        // 输出日志信息
        this->__WriteLog(stMsg);

        __FreeBuffer(stMsg.pcStr);
    }
}

int32_t CHTTPLogger::__FormatLog(HTTP_STRING_S& refMsg, ELogLevel enLevel, int32_t iErrCode, const char* pcLine, const char* pcFunc, const char* pcFmt, va_list ap)
{
    struct tm stTM;
	struct timeval stTV;
    int iLen;

    static const char* apcLogLevel[] = 
    {
        "DEBUG",
        "INFO ",
        "WARN ",
        "ERROR"
    };

    char* pcBuf = __AllocBuffer();
    if (pcBuf == nullptr)
    {
        return HTTP_ALLOC_MEMORY_FAIL;
    }
    
    (void)gettimeofday(&stTV, NULL);
	(void)localtime_r(&stTV.tv_sec, &stTM);

    // Date Time loglevel pid threadid err
    iLen = snprintf(pcBuf, LOG_LINE_MAX, "%04d%02d%02d %02d:%02d:%02d.%06lu %s [%d] [%ld] %d ", 
                    stTM.tm_year + 1900, stTM.tm_mon + 1, stTM.tm_mday, 
                    stTM.tm_hour, stTM.tm_min, stTM.tm_sec, stTV.tv_usec, 
                    apcLogLevel[(int)enLevel], getpid(), syscall(SYS_gettid), iErrCode);
	iLen += vsnprintf(pcBuf + iLen, LOG_LINE_MAX - iLen, pcFmt, ap);
	iLen += snprintf(pcBuf + iLen, LOG_LINE_MAX - iLen, ", %s, %s\n", pcLine, pcFunc);

    refMsg.pcStr = pcBuf;
    refMsg.ulLen = (size_t)iLen;

    return HTTP_OK;
}

void CHTTPLogger::__WriteLog(HTTP_STRING_S& refMsg)
{
    if (m_ulFileSize + refMsg.ulLen > m_ulMaxSize)
    {
        // 日志文件超过指定大小，需要重新创建文件，避免单个文件过大
        struct timeval stCurTV;
        (void)gettimeofday(&stCurTV, nullptr);

        char szTmpName[PATH_MAX];
        (void)snprintf(szTmpName, PATH_MAX, "%s/%s_%ld.log", m_szPath, HTTP_APP_NAME, stCurTV.tv_sec);
        (void)rename(m_szFileName, szTmpName);
        fclose(m_pstFile);
        m_pstFile = nullptr;
        m_ulFileSize = 0;
    }

    int32_t iErr = __OpenLogFile();
    if (iErr == HTTP_OK)
    {
        (void)fwrite(refMsg.pcStr, refMsg.ulLen, 1, m_pstFile);
        m_ulFileSize += refMsg.ulLen;
        fflush(m_pstFile);
    }
}


char* CHTTPLogger::__AllocBuffer()
{
    return new (std::nothrow)char[LOG_LINE_MAX];
}


void CHTTPLogger::__FreeBuffer(const char* pcBuff)
{
    if (pcBuff != nullptr)
    {
        delete [] pcBuff;
    }
}


int32_t CHTTPLogger::__OpenLogFile()
{
    if (m_pstFile != nullptr)
    {
        return HTTP_OK;
    }
    
    // 使用追加模式打开文件，退出重启之后，不会清除原有的日志文件
    m_pstFile = fopen(m_szFileName, "a+");
    if (m_pstFile == nullptr)
    {
        Syslog("fopen failed, %s\n", strerror(errno));
        return HTTP_OPEN_FILE_FAIL;
    }
    
    struct stat stStat;
    if (stat(m_szFileName, &stStat) != 0)
    {
        Syslog("fopen failed, %s\n", strerror(errno));
        fclose(m_pstFile);
        m_pstFile = nullptr;
        return HTTP_OPEN_FILE_FAIL;
    }

    m_ulFileSize = stStat.st_size;

    return HTTP_OK;
}